﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Iesi.Collections.Generic;
using Lincolnwood.Foundation.Common.Data;

namespace Lincolnwood.Foundation.Scheduling
{
    public abstract class RecurrencePattern : AuditableEntity<Guid>
    {
        private Iesi.Collections.Generic.ISet<PatternDeviation> _patternDeviations = new Iesi.Collections.Generic.SortedSet<PatternDeviation>();        
        private DateTime _endDate;
        private int _interval;

        protected RecurrencePattern()
        {
        }

        public RecurrencePattern(DateTime endDate, int interval) :
            this()
        {
            ValidateInterval(interval);
            this._endDate = GetEndOfDay(endDate);
            this._interval = interval;
        }

        private void ValidateInterval(int interval)
        {
            if (interval < 1)
                throw new ArgumentOutOfRangeException("interval", "Intervals supplied in constructing a RecurrencePattern must be greater than or equal to 1");
        }


        internal virtual Iesi.Collections.Generic.ISet<PatternDeviation> PatternDeviations
        {
            get
            {
                return this._patternDeviations;
            }

        }

        private DateTime GetBeginningOfDay(DateTime date)
        {
            // should return 12:00 am
            return date.Date;
        }

        private DateTime GetEndOfDay(DateTime date)
        {
            return date.Date.AddHours(23).AddMinutes(59).AddSeconds(59).AddMilliseconds(999);
        }

        public PatternDeviation AlterInstance(DateTime originalInstanceStartTime, string name, string description, DateTime startTime, DateTime endTime)
        {
            PatternDeviation exception = new InstanceModification(originalInstanceStartTime, name, description, startTime, endTime);

            //TODO: need to do validation and exception maintenance here.
            PatternDeviations.Add(exception);

            return exception;
        }

        public PatternDeviation RemoveInstance(DateTime originalInstanceStartTime)
        {
            PatternDeviation exception = new InstanceRemoval(originalInstanceStartTime);

            //TODO: need to do validation and exception maintenance here.
            PatternDeviations.Add(exception);

            return exception;
        }

        public virtual IList<IEventInstance> Expand(RecurrableEventDefinition eventContext, DateTime expandBegin, DateTime expandEnd)
        {
            expandBegin = GetBeginningOfDay(expandBegin);
            expandEnd = GetEndOfDay(expandEnd);

            // The CalendarEvent that is to be expanded
            RecurrableEventDefinition evt = eventContext;

            // Collection of expanded events
            List<IEventInstance> evtList = new List<IEventInstance>();

            TimeSpan eventDuration = eventContext.EndTime - eventContext.StartTime;

            // create new CalendarEvents: 1 event per recurrence period
            DateTime closestRecurrenceDate = this.FindClosestRecurrenceDate(eventContext, expandBegin);

            // if the period of expansion overlaps the recurrence period
            if (eventContext.StartTime <= expandEnd && expandBegin <= this.EndDate)
            {
                for (DateTime currentDate = closestRecurrenceDate;
                    currentDate <= this.EndDate && currentDate <= expandEnd;
                    currentDate = this.GetNextRecurrenceDate(currentDate))
                {
                    bool foundException = false;
                    TimeSpan eventLength = evt.EndTime.Subtract(evt.StartTime);
                    DateTime eventStartTime = currentDate;
                    DateTime eventEndTime = currentDate.Add(eventLength);

                    // check to see if this expanded event instance has a 
                    // pattern deviation associated with it whose OriginalDate matches
                    // the expanded event instance start date.
                    for (int idx = 0; idx < this.PatternDeviations.Count; idx++)
                    {
                        PatternDeviation exc = this.PatternDeviations.ElementAt<PatternDeviation>(idx);

                        // we can safely assume that there are no exceptions
                        // if we reach the end of the list of sorted
                        // exceptions without finding one or if we hit an event
                        // with a start time greater than the expanded event instance's
                        if (exc.OriginalDate > eventStartTime)
                            break;
                        else
                        {
                            // otherwise, if we find a match between the original date of the 
                            // exception and the currently expanded event instance
                            // and it is not deleted then add the exception
                            if (exc.OriginalDate.Date == eventStartTime.Date)
                            {
                                foundException = true;

                                InstanceModification instanceModification = exc as InstanceModification;
                                if (!(instanceModification == null) &&
                                        instanceModification.StartTime <= expandEnd &&
                                        instanceModification.StartTime >= expandBegin)
                                    evtList.Add(new EventInstance(evt, instanceModification.Name, instanceModification.Description, instanceModification.StartTime, instanceModification.EndTime));
                            }

                        }
                    }
                    if (!foundException)
                    {
                        // if no exception is associated with this event then generate an instance of the recurring event

                        // note there is a possibility that an exception is found and is not an InstanceModification
                        // by this logic it is assumed to be a deletion and no event instance is produced
                        IEventInstance recurringEvt = new EventInstance(evt, evt.Name, evt.Description, eventStartTime, eventEndTime);

                        evtList.Add(recurringEvt);
                    }
                }
            }
            return evtList;
        }

        public virtual DateTime EndDate
        {
            get { return _endDate; }
            set
            {
                _endDate = value;
            }
        }

        protected virtual int Interval
        {
            get { return _interval; }
            set { _interval = value; }
        }

        public abstract DateTime FindClosestRecurrenceDate(RecurrableEventDefinition eventContext, DateTime expandBegin);

        public abstract DateTime GetNextRecurrenceDate(DateTime baseDate);

    }
}
